/*-------------------------------------------------------------------------
 *
 * pg_aggregate.c
 *      routines to support manipulation of the pg_aggregate relation
 *
 * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *      src/backend/catalog/pg_aggregate.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/heapam.h"
#include "access/htup_details.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
#include "catalog/pg_aggregate.h"
#include "catalog/pg_language.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_proc_fn.h"
#include "catalog/pg_type.h"
#include "miscadmin.h"
#include "parser/parse_coerce.h"
#include "parser/parse_func.h"
#include "parser/parse_oper.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"


static Oid lookup_agg_function(List *fnName, int nargs, Oid *input_types,
                    Oid variadicArgType,
                    Oid *rettype);


/*
 * AggregateCreate
 */
ObjectAddress
AggregateCreate(const char *aggName,
                Oid aggNamespace,
                char aggKind,
                int numArgs,
                int numDirectArgs,
                oidvector *parameterTypes,
                Datum allParameterTypes,
                Datum parameterModes,
                Datum parameterNames,
                List *parameterDefaults,
                Oid variadicArgType,
                List *aggtransfnName,
                List *aggfinalfnName,
                List *aggcombinefnName,
                List *aggserialfnName,
                List *aggdeserialfnName,
                List *aggmtransfnName,
                List *aggminvtransfnName,
                List *aggmfinalfnName,
                bool finalfnExtraArgs,
                bool mfinalfnExtraArgs,
                List *aggsortopName,
                Oid aggTransType,
                int32 aggTransSpace,
                Oid aggmTransType,
                int32 aggmTransSpace,
                const char *agginitval,
                const char *aggminitval,
                char proparallel)
{// #lizard forgives
    Relation    aggdesc;
    HeapTuple    tup;
    bool        nulls[Natts_pg_aggregate];
    Datum        values[Natts_pg_aggregate];
    Form_pg_proc proc;
    Oid            transfn;
    Oid            finalfn = InvalidOid;    /* can be omitted */
    Oid            combinefn = InvalidOid; /* can be omitted */
    Oid            serialfn = InvalidOid;    /* can be omitted */
    Oid            deserialfn = InvalidOid;    /* can be omitted */
    Oid            mtransfn = InvalidOid;    /* can be omitted */
    Oid            minvtransfn = InvalidOid;    /* can be omitted */
    Oid            mfinalfn = InvalidOid;    /* can be omitted */
    Oid            sortop = InvalidOid;    /* can be omitted */
    Oid           *aggArgTypes = parameterTypes->values;
    bool        hasPolyArg;
    bool        hasInternalArg;
    bool        mtransIsStrict = false;
    Oid            rettype;
    Oid            finaltype;
    Oid            fnArgs[FUNC_MAX_ARGS];
    int            nargs_transfn;
    int            nargs_finalfn;
    Oid            procOid;
    TupleDesc    tupDesc;
    int            i;
    ObjectAddress myself,
                referenced;
    AclResult    aclresult;

    /* sanity checks (caller should have caught these) */
    if (!aggName)
        elog(ERROR, "no aggregate name supplied");

    if (!aggtransfnName)
        elog(ERROR, "aggregate must have a transition function");

    if (numDirectArgs < 0 || numDirectArgs > numArgs)
        elog(ERROR, "incorrect number of direct args for aggregate");

    /*
     * Aggregates can have at most FUNC_MAX_ARGS-1 args, else the transfn
     * and/or finalfn will be unrepresentable in pg_proc.  We must check now
     * to protect fixed-size arrays here and possibly in called functions.
     */
    if (numArgs < 0 || numArgs > FUNC_MAX_ARGS - 1)
        ereport(ERROR,
                (errcode(ERRCODE_TOO_MANY_ARGUMENTS),
                 errmsg_plural("aggregates cannot have more than %d argument",
                               "aggregates cannot have more than %d arguments",
                               FUNC_MAX_ARGS - 1,
                               FUNC_MAX_ARGS - 1)));

    /* check for polymorphic and INTERNAL arguments */
    hasPolyArg = false;
    hasInternalArg = false;
    for (i = 0; i < numArgs; i++)
    {
        if (IsPolymorphicType(aggArgTypes[i]))
            hasPolyArg = true;
        else if (aggArgTypes[i] == INTERNALOID)
            hasInternalArg = true;
    }

    /*
     * If transtype is polymorphic, must have polymorphic argument also; else
     * we will have no way to deduce the actual transtype.
     */
    if (IsPolymorphicType(aggTransType) && !hasPolyArg)
        ereport(ERROR,
                (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
                 errmsg("cannot determine transition data type"),
                 errdetail("An aggregate using a polymorphic transition type must have at least one polymorphic argument.")));

    /*
     * Likewise for moving-aggregate transtype, if any
     */
    if (OidIsValid(aggmTransType) &&
        IsPolymorphicType(aggmTransType) && !hasPolyArg)
        ereport(ERROR,
                (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
                 errmsg("cannot determine transition data type"),
                 errdetail("An aggregate using a polymorphic transition type must have at least one polymorphic argument.")));

    /*
     * An ordered-set aggregate that is VARIADIC must be VARIADIC ANY.  In
     * principle we could support regular variadic types, but it would make
     * things much more complicated because we'd have to assemble the correct
     * subsets of arguments into array values.  Since no standard aggregates
     * have use for such a case, we aren't bothering for now.
     */
    if (AGGKIND_IS_ORDERED_SET(aggKind) && OidIsValid(variadicArgType) &&
        variadicArgType != ANYOID)
        ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                 errmsg("a variadic ordered-set aggregate must use VARIADIC type ANY")));

    /*
     * If it's a hypothetical-set aggregate, there must be at least as many
     * direct arguments as aggregated ones, and the last N direct arguments
     * must match the aggregated ones in type.  (We have to check this again
     * when the aggregate is called, in case ANY is involved, but it makes
     * sense to reject the aggregate definition now if the declared arg types
     * don't match up.)  It's unconditionally OK if numDirectArgs == numArgs,
     * indicating that the grammar merged identical VARIADIC entries from both
     * lists.  Otherwise, if the agg is VARIADIC, then we had VARIADIC only on
     * the aggregated side, which is not OK.  Otherwise, insist on the last N
     * parameter types on each side matching exactly.
     */
    if (aggKind == AGGKIND_HYPOTHETICAL &&
        numDirectArgs < numArgs)
    {
        int            numAggregatedArgs = numArgs - numDirectArgs;

        if (OidIsValid(variadicArgType) ||
            numDirectArgs < numAggregatedArgs ||
            memcmp(aggArgTypes + (numDirectArgs - numAggregatedArgs),
                   aggArgTypes + numDirectArgs,
                   numAggregatedArgs * sizeof(Oid)) != 0)
            ereport(ERROR,
                    (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
                     errmsg("a hypothetical-set aggregate must have direct arguments matching its aggregated arguments")));
    }

    /*
     * Find the transfn.  For ordinary aggs, it takes the transtype plus all
     * aggregate arguments.  For ordered-set aggs, it takes the transtype plus
     * all aggregated args, but not direct args.  However, we have to treat
     * specially the case where a trailing VARIADIC item is considered to
     * cover both direct and aggregated args.
     */
    if (AGGKIND_IS_ORDERED_SET(aggKind))
    {
        if (numDirectArgs < numArgs)
            nargs_transfn = numArgs - numDirectArgs + 1;
        else
        {
            /* special case with VARIADIC last arg */
            Assert(variadicArgType != InvalidOid);
            nargs_transfn = 2;
        }
        fnArgs[0] = aggTransType;
        memcpy(fnArgs + 1, aggArgTypes + (numArgs - (nargs_transfn - 1)),
               (nargs_transfn - 1) * sizeof(Oid));
    }
    else
    {
        nargs_transfn = numArgs + 1;
        fnArgs[0] = aggTransType;
        memcpy(fnArgs + 1, aggArgTypes, numArgs * sizeof(Oid));
    }
    transfn = lookup_agg_function(aggtransfnName, nargs_transfn,
                                  fnArgs, variadicArgType,
                                  &rettype);

    /*
     * Return type of transfn (possibly after refinement by
     * enforce_generic_type_consistency, if transtype isn't polymorphic) must
     * exactly match declared transtype.
     *
     * In the non-polymorphic-transtype case, it might be okay to allow a
     * rettype that's binary-coercible to transtype, but I'm not quite
     * convinced that it's either safe or useful.  When transtype is
     * polymorphic we *must* demand exact equality.
     */
    if (rettype != aggTransType)
        ereport(ERROR,
                (errcode(ERRCODE_DATATYPE_MISMATCH),
                 errmsg("return type of transition function %s is not %s",
                        NameListToString(aggtransfnName),
                        format_type_be(aggTransType))));

    tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(transfn));
    if (!HeapTupleIsValid(tup))
        elog(ERROR, "cache lookup failed for function %u", transfn);
    proc = (Form_pg_proc) GETSTRUCT(tup);

    /*
     * If the transfn is strict and the initval is NULL, make sure first input
     * type and transtype are the same (or at least binary-compatible), so
     * that it's OK to use the first input value as the initial transValue.
     */
    if (proc->proisstrict && agginitval == NULL)
    {
        if (numArgs < 1 ||
            !IsBinaryCoercible(aggArgTypes[0], aggTransType))
            ereport(ERROR,
                    (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
                     errmsg("must not omit initial value when transition function is strict and transition type is not compatible with input type")));
    }

    ReleaseSysCache(tup);

    /* handle moving-aggregate transfn, if supplied */
    if (aggmtransfnName)
    {
        /*
         * The arguments are the same as for the regular transfn, except that
         * the transition data type might be different.  So re-use the fnArgs
         * values set up above, except for that one.
         */
        Assert(OidIsValid(aggmTransType));
        fnArgs[0] = aggmTransType;

        mtransfn = lookup_agg_function(aggmtransfnName, nargs_transfn,
                                       fnArgs, variadicArgType,
                                       &rettype);

        /* As above, return type must exactly match declared mtranstype. */
        if (rettype != aggmTransType)
            ereport(ERROR,
                    (errcode(ERRCODE_DATATYPE_MISMATCH),
                     errmsg("return type of transition function %s is not %s",
                            NameListToString(aggmtransfnName),
                            format_type_be(aggmTransType))));

        tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(mtransfn));
        if (!HeapTupleIsValid(tup))
            elog(ERROR, "cache lookup failed for function %u", mtransfn);
        proc = (Form_pg_proc) GETSTRUCT(tup);

        /*
         * If the mtransfn is strict and the minitval is NULL, check first
         * input type and mtranstype are binary-compatible.
         */
        if (proc->proisstrict && aggminitval == NULL)
        {
            if (numArgs < 1 ||
                !IsBinaryCoercible(aggArgTypes[0], aggmTransType))
                ereport(ERROR,
                        (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
                         errmsg("must not omit initial value when transition function is strict and transition type is not compatible with input type")));
        }

        /* Remember if mtransfn is strict; we may need this below */
        mtransIsStrict = proc->proisstrict;

        ReleaseSysCache(tup);
    }

    /* handle minvtransfn, if supplied */
    if (aggminvtransfnName)
    {
        /*
         * This must have the same number of arguments with the same types as
         * the forward transition function, so just re-use the fnArgs data.
         */
        Assert(aggmtransfnName);

        minvtransfn = lookup_agg_function(aggminvtransfnName, nargs_transfn,
                                          fnArgs, variadicArgType,
                                          &rettype);

        /* As above, return type must exactly match declared mtranstype. */
        if (rettype != aggmTransType)
            ereport(ERROR,
                    (errcode(ERRCODE_DATATYPE_MISMATCH),
                     errmsg("return type of inverse transition function %s is not %s",
                            NameListToString(aggminvtransfnName),
                            format_type_be(aggmTransType))));

        tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(minvtransfn));
        if (!HeapTupleIsValid(tup))
            elog(ERROR, "cache lookup failed for function %u", minvtransfn);
        proc = (Form_pg_proc) GETSTRUCT(tup);

        /*
         * We require the strictness settings of the forward and inverse
         * transition functions to agree.  This saves having to handle
         * assorted special cases at execution time.
         */
        if (proc->proisstrict != mtransIsStrict)
            ereport(ERROR,
                    (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
                     errmsg("strictness of aggregate's forward and inverse transition functions must match")));

        ReleaseSysCache(tup);
    }

    /* handle finalfn, if supplied */
    if (aggfinalfnName)
    {
        /*
         * If finalfnExtraArgs is specified, the transfn takes the transtype
         * plus all args; otherwise, it just takes the transtype plus any
         * direct args.  (Non-direct args are useless at runtime, and are
         * actually passed as NULLs, but we may need them in the function
         * signature to allow resolution of a polymorphic agg's result type.)
         */
        Oid            ffnVariadicArgType = variadicArgType;

        fnArgs[0] = aggTransType;
        memcpy(fnArgs + 1, aggArgTypes, numArgs * sizeof(Oid));
        if (finalfnExtraArgs)
            nargs_finalfn = numArgs + 1;
        else
        {
            nargs_finalfn = numDirectArgs + 1;
            if (numDirectArgs < numArgs)
            {
                /* variadic argument doesn't affect finalfn */
                ffnVariadicArgType = InvalidOid;
            }
        }

        finalfn = lookup_agg_function(aggfinalfnName, nargs_finalfn,
                                      fnArgs, ffnVariadicArgType,
                                      &finaltype);

        /*
         * When finalfnExtraArgs is specified, the finalfn will certainly be
         * passed at least one null argument, so complain if it's strict.
         * Nothing bad would happen at runtime (you'd just get a null result),
         * but it's surely not what the user wants, so let's complain now.
         */
        if (finalfnExtraArgs && func_strict(finalfn))
            ereport(ERROR,
                    (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
                     errmsg("final function with extra arguments must not be declared STRICT")));
    }
    else
    {
        /*
         * If no finalfn, aggregate result type is type of the state value
         */
        finaltype = aggTransType;
    }
    Assert(OidIsValid(finaltype));

    /* handle the combinefn, if supplied */
    if (aggcombinefnName)
    {
        Oid            combineType;

        /*
         * Combine function must have 2 argument, each of which is the trans
         * type
         */
        fnArgs[0] = aggTransType;
        fnArgs[1] = aggTransType;

        combinefn = lookup_agg_function(aggcombinefnName, 2, fnArgs,
                                        variadicArgType, &combineType);

        /* Ensure the return type matches the aggregates trans type */
        if (combineType != aggTransType)
            ereport(ERROR,
                    (errcode(ERRCODE_DATATYPE_MISMATCH),
                     errmsg("return type of combine function %s is not %s",
                            NameListToString(aggcombinefnName),
                            format_type_be(aggTransType))));

        /*
         * A combine function to combine INTERNAL states must accept nulls and
         * ensure that the returned state is in the correct memory context.
         */
        if (aggTransType == INTERNALOID && func_strict(combinefn))
            ereport(ERROR,
                    (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
                     errmsg("combine function with transition type %s must not be declared STRICT",
                            format_type_be(aggTransType))));

    }

    /*
     * Validate the serialization function, if present.
     */
    if (aggserialfnName)
    {
        fnArgs[0] = INTERNALOID;

        serialfn = lookup_agg_function(aggserialfnName, 1,
                                       fnArgs, variadicArgType,
                                       &rettype);

        if (rettype != BYTEAOID)
            ereport(ERROR,
                    (errcode(ERRCODE_DATATYPE_MISMATCH),
                     errmsg("return type of serialization function %s is not %s",
                            NameListToString(aggserialfnName),
                            format_type_be(BYTEAOID))));
    }

    /*
     * Validate the deserialization function, if present.
     */
    if (aggdeserialfnName)
    {
        fnArgs[0] = BYTEAOID;
        fnArgs[1] = INTERNALOID;    /* dummy argument for type safety */

        deserialfn = lookup_agg_function(aggdeserialfnName, 2,
                                         fnArgs, variadicArgType,
                                         &rettype);

        if (rettype != INTERNALOID)
            ereport(ERROR,
                    (errcode(ERRCODE_DATATYPE_MISMATCH),
                     errmsg("return type of deserialization function %s is not %s",
                            NameListToString(aggdeserialfnName),
                            format_type_be(INTERNALOID))));
    }

    /*
     * If finaltype (i.e. aggregate return type) is polymorphic, inputs must
     * be polymorphic also, else parser will fail to deduce result type.
     * (Note: given the previous test on transtype and inputs, this cannot
     * happen, unless someone has snuck a finalfn definition into the catalogs
     * that itself violates the rule against polymorphic result with no
     * polymorphic input.)
     */
    if (IsPolymorphicType(finaltype) && !hasPolyArg)
        ereport(ERROR,
                (errcode(ERRCODE_DATATYPE_MISMATCH),
                 errmsg("cannot determine result data type"),
                 errdetail("An aggregate returning a polymorphic type "
                           "must have at least one polymorphic argument.")));

    /*
     * Also, the return type can't be INTERNAL unless there's at least one
     * INTERNAL argument.  This is the same type-safety restriction we enforce
     * for regular functions, but at the level of aggregates.  We must test
     * this explicitly because we allow INTERNAL as the transtype.
     */
    if (finaltype == INTERNALOID && !hasInternalArg)
        ereport(ERROR,
                (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
                 errmsg("unsafe use of pseudo-type \"internal\""),
                 errdetail("A function returning \"internal\" must have at least one \"internal\" argument.")));

    /*
     * If a moving-aggregate implementation is supplied, look up its finalfn
     * if any, and check that the implied aggregate result type matches the
     * plain implementation.
     */
    if (OidIsValid(aggmTransType))
    {
        /* handle finalfn, if supplied */
        if (aggmfinalfnName)
        {
            /*
             * The arguments are figured the same way as for the regular
             * finalfn, but using aggmTransType and mfinalfnExtraArgs.
             */
            Oid            ffnVariadicArgType = variadicArgType;

            fnArgs[0] = aggmTransType;
            memcpy(fnArgs + 1, aggArgTypes, numArgs * sizeof(Oid));
            if (mfinalfnExtraArgs)
                nargs_finalfn = numArgs + 1;
            else
            {
                nargs_finalfn = numDirectArgs + 1;
                if (numDirectArgs < numArgs)
                {
                    /* variadic argument doesn't affect finalfn */
                    ffnVariadicArgType = InvalidOid;
                }
            }

            mfinalfn = lookup_agg_function(aggmfinalfnName, nargs_finalfn,
                                           fnArgs, ffnVariadicArgType,
                                           &rettype);

            /* As above, check strictness if mfinalfnExtraArgs is given */
            if (mfinalfnExtraArgs && func_strict(mfinalfn))
                ereport(ERROR,
                        (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
                         errmsg("final function with extra arguments must not be declared STRICT")));
        }
        else
        {
            /*
             * If no finalfn, aggregate result type is type of the state value
             */
            rettype = aggmTransType;
        }
        Assert(OidIsValid(rettype));
        if (rettype != finaltype)
            ereport(ERROR,
                    (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
                     errmsg("moving-aggregate implementation returns type %s, but plain implementation returns type %s",
                            format_type_be(aggmTransType),
                            format_type_be(aggTransType))));
    }

    /* handle sortop, if supplied */
    if (aggsortopName)
    {
        if (numArgs != 1)
            ereport(ERROR,
                    (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
                     errmsg("sort operator can only be specified for single-argument aggregates")));
        sortop = LookupOperName(NULL, aggsortopName,
                                aggArgTypes[0], aggArgTypes[0],
                                false, -1);
    }

    /*
     * permission checks on used types
     */
    for (i = 0; i < numArgs; i++)
    {
        aclresult = pg_type_aclcheck(aggArgTypes[i], GetUserId(), ACL_USAGE);
        if (aclresult != ACLCHECK_OK)
            aclcheck_error_type(aclresult, aggArgTypes[i]);
    }

    aclresult = pg_type_aclcheck(aggTransType, GetUserId(), ACL_USAGE);
    if (aclresult != ACLCHECK_OK)
        aclcheck_error_type(aclresult, aggTransType);

    if (OidIsValid(aggmTransType))
    {
        aclresult = pg_type_aclcheck(aggmTransType, GetUserId(), ACL_USAGE);
        if (aclresult != ACLCHECK_OK)
            aclcheck_error_type(aclresult, aggmTransType);
    }

    aclresult = pg_type_aclcheck(finaltype, GetUserId(), ACL_USAGE);
    if (aclresult != ACLCHECK_OK)
        aclcheck_error_type(aclresult, finaltype);


    /*
     * Everything looks okay.  Try to create the pg_proc entry for the
     * aggregate.  (This could fail if there's already a conflicting entry.)
     */

    myself = ProcedureCreate(aggName,
                             aggNamespace,
                             false, /* no replacement */
                             false, /* doesn't return a set */
                             finaltype, /* returnType */
                             GetUserId(),    /* proowner */
                             INTERNALlanguageId,    /* languageObjectId */
                             InvalidOid,    /* no validator */
                             "aggregate_dummy", /* placeholder proc */
                             NULL,    /* probin */
                             true,    /* isAgg */
                             false, /* isWindowFunc */
                             false, /* security invoker (currently not
                                     * definable for agg) */
                             false, /* isLeakProof */
                             false, /* isStrict (not needed for agg) */
                             PROVOLATILE_IMMUTABLE, /* volatility (not needed
                                                     * for agg) */
                             proparallel,
                             parameterTypes,    /* paramTypes */
                             allParameterTypes, /* allParamTypes */
                             parameterModes,    /* parameterModes */
                             parameterNames,    /* parameterNames */
                             parameterDefaults, /* parameterDefaults */
                             PointerGetDatum(NULL), /* trftypes */
                             PointerGetDatum(NULL), /* proconfig */
                             1, /* procost */
                             0);    /* prorows */
    procOid = myself.objectId;

    /*
     * Okay to create the pg_aggregate entry.
     */

    /* initialize nulls and values */
    for (i = 0; i < Natts_pg_aggregate; i++)
    {
        nulls[i] = false;
        values[i] = (Datum) NULL;
    }
    values[Anum_pg_aggregate_aggfnoid - 1] = ObjectIdGetDatum(procOid);
    values[Anum_pg_aggregate_aggkind - 1] = CharGetDatum(aggKind);
    values[Anum_pg_aggregate_aggnumdirectargs - 1] = Int16GetDatum(numDirectArgs);
    values[Anum_pg_aggregate_aggtransfn - 1] = ObjectIdGetDatum(transfn);
    values[Anum_pg_aggregate_aggfinalfn - 1] = ObjectIdGetDatum(finalfn);
    values[Anum_pg_aggregate_aggcombinefn - 1] = ObjectIdGetDatum(combinefn);
    values[Anum_pg_aggregate_aggserialfn - 1] = ObjectIdGetDatum(serialfn);
    values[Anum_pg_aggregate_aggdeserialfn - 1] = ObjectIdGetDatum(deserialfn);
    values[Anum_pg_aggregate_aggmtransfn - 1] = ObjectIdGetDatum(mtransfn);
    values[Anum_pg_aggregate_aggminvtransfn - 1] = ObjectIdGetDatum(minvtransfn);
    values[Anum_pg_aggregate_aggmfinalfn - 1] = ObjectIdGetDatum(mfinalfn);
    values[Anum_pg_aggregate_aggfinalextra - 1] = BoolGetDatum(finalfnExtraArgs);
    values[Anum_pg_aggregate_aggmfinalextra - 1] = BoolGetDatum(mfinalfnExtraArgs);
    values[Anum_pg_aggregate_aggsortop - 1] = ObjectIdGetDatum(sortop);
    values[Anum_pg_aggregate_aggtranstype - 1] = ObjectIdGetDatum(aggTransType);
    values[Anum_pg_aggregate_aggtransspace - 1] = Int32GetDatum(aggTransSpace);
    values[Anum_pg_aggregate_aggmtranstype - 1] = ObjectIdGetDatum(aggmTransType);
    values[Anum_pg_aggregate_aggmtransspace - 1] = Int32GetDatum(aggmTransSpace);
    if (agginitval)
        values[Anum_pg_aggregate_agginitval - 1] = CStringGetTextDatum(agginitval);
    else
        nulls[Anum_pg_aggregate_agginitval - 1] = true;
    if (aggminitval)
        values[Anum_pg_aggregate_aggminitval - 1] = CStringGetTextDatum(aggminitval);
    else
        nulls[Anum_pg_aggregate_aggminitval - 1] = true;

    aggdesc = heap_open(AggregateRelationId, RowExclusiveLock);
    tupDesc = aggdesc->rd_att;

    tup = heap_form_tuple(tupDesc, values, nulls);
    CatalogTupleInsert(aggdesc, tup);

    heap_close(aggdesc, RowExclusiveLock);

    /*
     * Create dependencies for the aggregate (above and beyond those already
     * made by ProcedureCreate).  Note: we don't need an explicit dependency
     * on aggTransType since we depend on it indirectly through transfn.
     * Likewise for aggmTransType using the mtransfunc, if it exists.
     */

    /* Depends on transition function */
    referenced.classId = ProcedureRelationId;
    referenced.objectId = transfn;
    referenced.objectSubId = 0;
    recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);

    /* Depends on final function, if any */
    if (OidIsValid(finalfn))
    {
        referenced.classId = ProcedureRelationId;
        referenced.objectId = finalfn;
        referenced.objectSubId = 0;
        recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
    }

    /* Depends on combine function, if any */
    if (OidIsValid(combinefn))
    {
        referenced.classId = ProcedureRelationId;
        referenced.objectId = combinefn;
        referenced.objectSubId = 0;
        recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
    }

    /* Depends on serialization function, if any */
    if (OidIsValid(serialfn))
    {
        referenced.classId = ProcedureRelationId;
        referenced.objectId = serialfn;
        referenced.objectSubId = 0;
        recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
    }

    /* Depends on deserialization function, if any */
    if (OidIsValid(deserialfn))
    {
        referenced.classId = ProcedureRelationId;
        referenced.objectId = deserialfn;
        referenced.objectSubId = 0;
        recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
    }

    /* Depends on forward transition function, if any */
    if (OidIsValid(mtransfn))
    {
        referenced.classId = ProcedureRelationId;
        referenced.objectId = mtransfn;
        referenced.objectSubId = 0;
        recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
    }

    /* Depends on inverse transition function, if any */
    if (OidIsValid(minvtransfn))
    {
        referenced.classId = ProcedureRelationId;
        referenced.objectId = minvtransfn;
        referenced.objectSubId = 0;
        recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
    }

    /* Depends on final function, if any */
    if (OidIsValid(mfinalfn))
    {
        referenced.classId = ProcedureRelationId;
        referenced.objectId = mfinalfn;
        referenced.objectSubId = 0;
        recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
    }

    /* Depends on sort operator, if any */
    if (OidIsValid(sortop))
    {
        referenced.classId = OperatorRelationId;
        referenced.objectId = sortop;
        referenced.objectSubId = 0;
        recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
    }

    return myself;
}

/*
 * lookup_agg_function
 * common code for finding transfn, invtransfn, finalfn, and combinefn
 *
 * Returns OID of function, and stores its return type into *rettype
 *
 * NB: must not scribble on input_types[], as we may re-use those
 */
static Oid
lookup_agg_function(List *fnName,
                    int nargs,
                    Oid *input_types,
                    Oid variadicArgType,
                    Oid *rettype)
{// #lizard forgives
    Oid            fnOid;
    bool        retset;
    int            nvargs;
    Oid            vatype;
    Oid           *true_oid_array;
    FuncDetailCode fdresult;
    AclResult    aclresult;
    int            i;

    /*
     * func_get_detail looks up the function in the catalogs, does
     * disambiguation for polymorphic functions, handles inheritance, and
     * returns the funcid and type and set or singleton status of the
     * function's return value.  it also returns the true argument types to
     * the function.
     */
    fdresult = func_get_detail(fnName, NIL, NIL,
                               nargs, input_types, false, false,
                               &fnOid, rettype, &retset,
                               &nvargs, &vatype,
                               &true_oid_array, NULL);

    /* only valid case is a normal function not returning a set */
    if (fdresult != FUNCDETAIL_NORMAL || !OidIsValid(fnOid))
        ereport(ERROR,
                (errcode(ERRCODE_UNDEFINED_FUNCTION),
                 errmsg("function %s does not exist",
                        func_signature_string(fnName, nargs,
                                              NIL, input_types))));
    if (retset)
        ereport(ERROR,
                (errcode(ERRCODE_DATATYPE_MISMATCH),
                 errmsg("function %s returns a set",
                        func_signature_string(fnName, nargs,
                                              NIL, input_types))));

    /*
     * If the agg is declared to take VARIADIC ANY, the underlying functions
     * had better be declared that way too, else they may receive too many
     * parameters; but func_get_detail would have been happy with plain ANY.
     * (Probably nothing very bad would happen, but it wouldn't work as the
     * user expects.)  Other combinations should work without any special
     * pushups, given that we told func_get_detail not to expand VARIADIC.
     */
    if (variadicArgType == ANYOID && vatype != ANYOID)
        ereport(ERROR,
                (errcode(ERRCODE_DATATYPE_MISMATCH),
                 errmsg("function %s must accept VARIADIC ANY to be used in this aggregate",
                        func_signature_string(fnName, nargs,
                                              NIL, input_types))));

    /*
     * If there are any polymorphic types involved, enforce consistency, and
     * possibly refine the result type.  It's OK if the result is still
     * polymorphic at this point, though.
     */
    *rettype = enforce_generic_type_consistency(input_types,
                                                true_oid_array,
                                                nargs,
                                                *rettype,
                                                true);

    /*
     * func_get_detail will find functions requiring run-time argument type
     * coercion, but nodeAgg.c isn't prepared to deal with that
     */
    for (i = 0; i < nargs; i++)
    {
        if (!IsBinaryCoercible(input_types[i], true_oid_array[i]))
            ereport(ERROR,
                    (errcode(ERRCODE_DATATYPE_MISMATCH),
                     errmsg("function %s requires run-time type coercion",
                            func_signature_string(fnName, nargs,
                                                  NIL, true_oid_array))));
    }

    /* Check aggregate creator has permission to call the function */
    aclresult = pg_proc_aclcheck(fnOid, GetUserId(), ACL_EXECUTE);
    if (aclresult != ACLCHECK_OK)
        aclcheck_error(aclresult, ACL_KIND_PROC, get_func_name(fnOid));

    return fnOid;
}
